package blockchain

import (
	"bytes"
	"crypto/sha256"
	"encoding/gob"
	"goblockchain/merkletree"
	"goblockchain/transaction"
	"goblockchain/utils"
	"time"
)

/*
区块链结构体有时间戳，本身的哈希值，指向上一个区块的哈希这三个属性构成头部信息
Nonce就是节点寻找到的作为卷王的证据。Target就是我们前文说到的目标难度值，
将它保存到区块中便于其他节点快速验证Nonce是否正确。
*/
type Block struct {
	Timestamp    int64
	Hash         []byte
	PrevHash     []byte
	Height       int64 // This is new.
	Target       []byte
	Nonce        int64
	Transactions []*transaction.Transaction
	MTree        *merkletree.MerkleTree
}

// 返回区块哈希的函数，创建一个函数来协助处理区块中交易信息的序列化。
func (b *Block) BackTrasactionSummary() []byte {
	txIDs := make([][]byte, 0)
	for _, tx := range b.Transactions {
		txIDs = append(txIDs, tx.ID)
	}
	summary := bytes.Join(txIDs, []byte{})
	return summary
}

/*
information变量是将区块的各项属性串联之后的字节串。这里提醒一下bytes.Join可以将多个字节串连接，
第二个参数是将字节串连接时的分隔符，这里设置为[]byte{}即为空，
ToHexInt将int64转换为字节串类型。然后我们对information做哈希就可以得到区块的哈希值了。
*/
func (b *Block) SetHash() {
	information := bytes.Join([][]byte{utils.ToHexInt(b.Timestamp), b.PrevHash, b.Target, utils.ToHexInt(b.Nonce), b.BackTrasactionSummary(), b.MTree.RootNode.Data}, []byte{})
	hash := sha256.Sum256(information)
	b.Hash = hash[:]
}

// 区块创建
func CreateBlock(prevhash []byte, height int64, txs []*transaction.Transaction) *Block {
	block := Block{time.Now().Unix(), []byte{}, prevhash, height, []byte{}, 0, txs, merkletree.CrateMerkleTree(txs)}
	block.Target = block.GetTarget()
	block.Nonce = block.FindNonce()
	block.SetHash()
	return &block
}

/*
在创建一个区块时一定要引用前一个区块的哈希值，这里会有一个问题，那就是区块链中的第一个区块怎么创建？
其实，在区块链中有一个创世区块，随着区块链的创建而添加，它指向的上一个区块的哈希值为空。
*/
func GenesisBlock(address []byte) *Block {
	tx := transaction.BaseTx(address)
	genesis := CreateBlock([]byte("Leo Cao is awesome!"), 0, []*transaction.Transaction{tx})
	genesis.SetHash()
	return genesis
}

// 用于序列化区块生成字节串
func (b *Block) Serialize() []byte {
	var res bytes.Buffer
	encoder := gob.NewEncoder(&res)
	err := encoder.Encode(b)
	utils.Handle(err)
	return res.Bytes()
}

// 反序列化区块 Badger的键值对只支持字节串存储形式
func DeSerializeBlock(data []byte) *Block {
	var block Block
	decoder := gob.NewDecoder(bytes.NewReader(data))
	err := decoder.Decode(&block)
	utils.Handle(err)
	return &block
}
